﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ukagaka.Named.Ghost.Shiori
{
	/// <summary>
	/// 栞との接続を表現します
	/// </summary>
	public class ShioriConnection
	{
		/// <summary>
		/// 受信ストリームを取得します
		/// </summary>
		private readonly Stream InStream;

		/// <summary>
		/// 送信ストリームを取得します
		/// </summary>
		private readonly Stream OutStream;

		/// <summary>
		/// ホストからのリクエスト受信待機の処理です
		/// </summary>
		private readonly Task Task;

		/// <summary>
		/// ホストと接続しているかどうかを取得します
		/// </summary>
		public bool IsRunning
		{
			get;
			private set;
		}

		/// <summary>
		/// ホストから Load を受信したときに処理します
		/// </summary>
		public event EventHandler<EventArgs> Load;

		/// <summary>
		/// ホストから Request を受信したときに処理します
		/// </summary>
		public event EventHandler<ShioriEventArgs> Request;

		/// <summary>
		/// ホストから Unload を受信したときに処理します
		/// </summary>
		public event EventHandler<EventArgs> Unload;

		/// <summary>
		/// ホストから Load を受信したときに処理します
		/// </summary>
		/// <param name="e">イベント情報</param>
		protected void OnLoad( EventArgs e )
		{
			if ( null != Load )
			{
				Load( this, e );
			}
		}

		/// <summary>
		/// ホストから Request を受信したときに処理します
		/// </summary>
		/// <param name="e">イベント情報</param>
		protected void OnRequest( ShioriEventArgs e )
		{
			if ( null != Request )
			{
				Request( this, e );
			}
		}

		/// <summary>
		/// ホストから Unload を受信したときに処理します
		/// </summary>
		/// <param name="e">イベント情報</param>
		protected void OnUnload( EventArgs e )
		{
			if ( null != Unload )
			{
				Unload( this, e );
			}
		}

		/// <summary>
		/// 栞との接続を初期化します
		/// </summary>
		public ShioriConnection( Stream inStream, Stream outStream )
		{
			InStream = inStream;
			OutStream = outStream;

			Task = new Task( Task_Run, TaskCreationOptions.LongRunning );
		}

		/// <summary>
		/// ユーザーコードがホストとの接続準備を完了したことを設定します
		/// </summary>
		public void Touch( )
		{
			if ( IsRunning )
			{
				return;
			}

			IsRunning = true;

			Task.Start( Task.Factory.Scheduler );
		}

		/// <summary>
		/// ホストからのリクエスト受信待機の処理です
		/// </summary>
		private void Task_Run( )
		{
			var bytes = new byte[ 8 * 1024 ];
			var received = new List<byte>( bytes.Length );

			while ( true )
			{
				var size = InStream.Read( bytes, 0, bytes.Length );
				received.AddRange( bytes.Take( size ) );

				var idx = received.IndexOf( ( byte )'\n' );

				if ( -1 == idx )
				{
					continue;
				}
				else
				{
					var line = received.Take( idx ).ToArray( );
					var splitted = Encoding.ASCII.GetString( line ).Split( new[ ] { ':' } );

					var function = new Function( splitted[ 0 ] );
					var length = int.Parse( splitted[ 1 ], NumberStyles.None, CultureInfo.InvariantCulture );

					if ( received.Count >= line.Length + length )
					{
						var requestBytes = received.Skip( line.Length ).Take( length ).ToArray( );
						received.RemoveRange( 0, line.Length + length );

						var request = Message.FromBytes( requestBytes ) as Request;
						Task_Run_Received( function, request );
					}
				}
			}
		}

		/// <summary>
		/// ホストからリクエスト受信時の処理です
		/// </summary>
		/// <param name="function">ファンクション</param>
		/// <param name="request">リクエスト</param>
		private void Task_Run_Received( Function function, Shiori.Request request )
		{
			switch ( function.Value )
			{
			default:
			case FunctionType.None:
				break;
			case FunctionType.Load:
				try
				{
					OnLoad( EventArgs.Empty );

					Send( FunctionType.Load, 1, Encoding.ASCII );
				}
				catch
				{
					Send( FunctionType.Load, 0, Encoding.ASCII );
				}
				break;
			case FunctionType.Request:
				var args = new ShioriEventArgs( request );
				OnRequest( args );
				if ( null == args.Response )
				{
					Send( function.Value, new Response( )
					{
						Encoding = request.Encoding,
						StatusCodeType = StatusCodeType.NoContent,
					} );
				}
				else
				{
					Send( function.Value, args.Response );
				}
				break;
			case FunctionType.Unload:
				try
				{
					OnUnload( EventArgs.Empty );

					Send( FunctionType.Unload, 1, Encoding.ASCII );
				}
				catch
				{
					Send( FunctionType.Unload, 0, Encoding.ASCII );
				}
				break;
			}
		}

		private void Send( FunctionType functionType, Message message )
		{
			Send( functionType, message.GetBytes( ) );
		}

		private void Send( FunctionType functionType, int data, Encoding encoding )
		{
			Send( functionType, encoding.GetBytes( data.ToString( CultureInfo.InvariantCulture ) ) );
		}

		private void Send( FunctionType functionType, byte[ ] data )
		{
			var crlf = Encoding.ASCII.GetBytes( "\r\n" );
			var header = Encoding.ASCII.GetBytes( string.Format( "{0}:{1}\r\n", new Function( functionType ), data.Length + crlf.Length ) );

			OutStream.Write( header, 0, header.Length );
			OutStream.Write( data, 0, data.Length );
			OutStream.Write( crlf, 0, crlf.Length );
		}
	}
}
